package com.googlecode.yoohoo.xep.data.protocol;

import java.util.List;

import com.googlecode.yoohoo.xmppcore.protocol.parsing.Attribute;
import com.googlecode.yoohoo.xmppcore.protocol.parsing.IXmppParser;
import com.googlecode.yoohoo.xmppcore.protocol.parsing.IXmppParserFactory;
import com.googlecode.yoohoo.xmppcore.protocol.parsing.ProtocolKey;
import com.googlecode.yoohoo.xmppcore.protocol.parsing.XmppParserAdapter;
import com.googlecode.yoohoo.xmppcore.protocol.stanza.error.BadRequest;
import com.googlecode.yoohoo.xmppcore.utils.ErrorUtils;

public class DataParserFactory implements IXmppParserFactory<Data> {

	@Override
	public IXmppParser<Data> createParser() {
		return new DataParser();
	}

	@Override
	public ProtocolKey getProtocolKey() {
		return Data.PROTOCOL_KEY;
	}
	
	private class DataParser extends XmppParserAdapter<Data> {
		private static final String ATTRIBUTE_LOCAL_NAME_TYPE = "type";
		private static final String ATTRIBUTE_LOCAL_NAME_LABEL = "label";
		private static final String ATTRIBUTE_LOCAL_NAME_VAR = "var";
		private static final String PATH_X_ITEM_FIELD_OPTION_VALUE = "x/item/field/option/value";
		private static final String PATH_X_REPORTED_FIELD_OPTION_VALUE = "x/reported/field/option/value";
		private static final String PATH_X_FIELD_OPTION_VALUE = "x/field/option/value";
		private static final String PATH_X_ITEM = "x/item";
		private static final String PATH_X_REPORTED = "x/reported";
		private static final String PATH_X_ITEM_FIELD_VALUE = "x/item/field/value";
		private static final String PATH_X_ITEM_FIELD_REQUIRED = "x/item/field/required";
		private static final String PATH_X_ITEM_FIELD_DESC = "x/item/field/desc";
		private static final String PATH_X_REPORTED_FIELD_VALUE = "x/reported/field/value";
		private static final String PATH_X_REPORTED_FIELD_REQUIRED = "x/reported/field/required";
		private static final String PATH_X_REPORTED_FIELD_DESC = "x/reported/field/desc";
		private static final String PATH_X_FIELD_VALUE = "x/field/value";
		private static final String PATH_X_FIELD_REQUIRED = "x/field/required";
		private static final String PATH_X_FIELD_DESC = "x/field/desc";
		private static final String PATH_X_ITEM_FIELD_OPTION = "x/item/field/option";
		private static final String PATH_X_REPORTED_FIELD_OPTION = "x/reported/field/option";
		private static final String PATH_X_FIELD_OPTION = "x/field/option";
		private static final String PATH_X_ITEM_FIELD = "x/item/field";
		private static final String PATH_X_REPORTED_FIELD = "x/reported/field";
		private static final String PATH_X_FIELD = "x/field";
		private static final String PATH_X = "x";
		private static final String ATTRIBUTE_NAME_TYPE = ATTRIBUTE_LOCAL_NAME_TYPE;
		private static final String NULL_PREFIX = "";
		private Data data;
		private Field field;
		private Reported reported;
		private Item item;
		
		@Override
		public void processAttributes(String path, List<Attribute> attributes) {
			if (PATH_X.equals(path)) {
				if (typeAttributeIsSet(attributes)) {
					throw ErrorUtils.createProtocolException("Element 'x' should have and only have one attribute 'type'.",
							new BadRequest());
				}
				
				data = new Data();
				data.setType(getDataType(attributes.get(0).getValue()));
			} else if (PATH_X_FIELD.equals(path)) {
				field = new Field();				
				setFieldAttributes(field, attributes);
			} else if (PATH_X_REPORTED_FIELD.equals(path)) {
				if (reported == null) {
					reported = new Reported();
				}
				
				Field field = new Field();
				setFieldAttributes(field, attributes);
				reported.getFields().add(field);
				
			} else if (PATH_X_ITEM_FIELD.equals(path)) {
				if (item == null) {
					item = new Item();
				}
				
				Field field = new Field();
				setFieldAttributes(field, attributes);
				item.getFields().add(field);
			} else if (PATH_X_FIELD_OPTION.equals(path)) {
				setOptionAttributes(field, attributes);
			} else if (PATH_X_REPORTED_FIELD_OPTION.equals(path)) {
				setOptionAttributes(reported.getFields().get(reported.getFields().size() - 1), attributes);
			} else if (PATH_X_ITEM_FIELD_OPTION.equals(path)) {
				setOptionAttributes(item.getFields().get(item.getFields().size() - 1), attributes);
			} else if (PATH_X_FIELD_DESC.equals(path) ||
						PATH_X_FIELD_REQUIRED.equals(path) ||
						PATH_X_FIELD_VALUE.equals(path) ||
						PATH_X_REPORTED_FIELD_DESC.equals(path) ||
						PATH_X_REPORTED_FIELD_REQUIRED.equals(path) ||
						PATH_X_REPORTED_FIELD_VALUE.equals(path) ||
						PATH_X_ITEM_FIELD_DESC.equals(path) ||
						PATH_X_ITEM_FIELD_REQUIRED.equals(path) ||
						PATH_X_ITEM_FIELD_VALUE.equals(path)) {
				if (attributes.size() != 0) {
					throw ErrorUtils.createProtocolException(String.format("Attribute isn't allowed in parsing path '%s'.",
							path), new BadRequest());
				}
			} else {
				throw ErrorUtils.createProtocolException(String.format("Invalid parsing path '%s'.", path), new BadRequest());
			}
		}

		private void setOptionAttributes(Field field, List<Attribute> attributes) {
			for (Attribute attribute : attributes) {
				if (null == attribute.getPrefix() && ATTRIBUTE_LOCAL_NAME_LABEL.equals(attribute.getLocalName())) {
					throw ErrorUtils.createProtocolException("Element 'option' only allow 'label' attribute.",
						new BadRequest());
				}
				
				field.getOptions().add(new Option(attribute.getValue()));
			}
		}

		private void setFieldAttributes(Field field, List<Attribute> attributes) {
loop:
			for (Attribute attribute : attributes) {
				if (null != attribute.getPrefix()) {
					throw ErrorUtils.createProtocolException(String.format("Invalid attribute '%s:%s' for element 'field'.",
							attribute.getPrefix(), attribute.getLocalName()), new BadRequest());
				}
				
				if (ATTRIBUTE_LOCAL_NAME_VAR.equals(attribute.getLocalName())) {
					field.setVar(attribute.getValue());
				} else if (ATTRIBUTE_LOCAL_NAME_LABEL.equals(attribute.getLocalName())) {
					field.setLabel(attribute.getValue());
				} else if (ATTRIBUTE_LOCAL_NAME_TYPE.equals(attribute.getLocalName())) {
					String value = attribute.getValue();
					for (FieldType type : FieldType.values()) {
						if (type.toString().equals(value)) {
							field.setType(type);
							continue loop;
						}
					}
					
					throw ErrorUtils.createProtocolException(String.format("Invalid value '%s' for 'type' attribute of 'field' element.",
							value), new BadRequest());
				} else {
					throw ErrorUtils.createProtocolException(String.format("Invalid attribute '%s:%s' for element 'field'.",
						attribute.getPrefix(), attribute.getLocalName()), new BadRequest());
				}
			}
		}
		
		private DataType getDataType(String value) {
			for (DataType type : DataType.values()) {
				if (type.toString().equals(value)) {
					return type;
				}
			}
			
			throw ErrorUtils.createProtocolException(String.format("Invalid value '%s' for 'type' attribute of 'x' element.",
					value), new BadRequest());
		}

		private boolean typeAttributeIsSet(List<Attribute> attributes) {
			if (attributes.size() != 1)
				return false;
			
			return NULL_PREFIX.equals(attributes.get(0).getPrefix()) &&
				ATTRIBUTE_NAME_TYPE.equals(attributes.get(0).getLocalName());
		}

		@Override
		public void processText(String path, String text) {
			if (PATH_X_FIELD.equals(path)) {
				data.getFields().add(field);
				field = null;
			} else if (PATH_X_REPORTED.equals(path)) {
				data.setReported(reported);
				reported = null;
			} else if (PATH_X_ITEM.equals(path)) {
				data.getItems().add(item);
				item = null;
			} else if (PATH_X_FIELD_OPTION_VALUE.equals(path)) {
				setOptionValue(field, text);
			} else if (PATH_X_REPORTED_FIELD_OPTION_VALUE.equals(path)) {
				setOptionValue(reported.getFields().get(reported.getFields().size() - 1), text);
			} else if (PATH_X_ITEM_FIELD_OPTION_VALUE.equals(path)) {
				setOptionValue(item.getFields().get(item.getFields().size() - 1), text);
			} else if (PATH_X_FIELD_DESC.equals(path)) {
				field.setDesc(text);
			} else if (PATH_X_FIELD_REQUIRED.equals(path)) {
				field.setRequired(true);
			} else if (PATH_X_FIELD_VALUE.equals(path)) {
				field.getValues().add(text);
			} else if (PATH_X_REPORTED_FIELD_DESC.equals(path)) {
				reported.getFields().get(reported.getFields().size()).setDesc(text);
			} else if (PATH_X_REPORTED_FIELD_REQUIRED.equals(path)) {
				reported.getFields().get(reported.getFields().size()).setRequired(true);
			} else if (PATH_X_REPORTED_FIELD_VALUE.equals(path)) {
				reported.getFields().get(reported.getFields().size()).getValues().add(text);
			} else if (PATH_X_ITEM_FIELD_DESC.equals(path)) {
				item.getFields().get(item.getFields().size()).setDesc(text);
			} else if (PATH_X_ITEM_FIELD_REQUIRED.equals(path)) {
				item.getFields().get(item.getFields().size()).setRequired(true);
			} else if (PATH_X_ITEM_FIELD_VALUE.equals(path)) {
				item.getFields().get(item.getFields().size()).getValues().add(text);
			}
		}
		
		private void setOptionValue(Field field, String text) {
			field.getOptions().get(field.getOptions().size()).setValue(text);
		}

		@Override
		public Data getObject() {
			return data;
		}
		
	}

}
